import { Injectable } from '@angular/core';

import { GeneralConfig } from '../config/general.config';
import { Token } from '../models/token.model';
import { CommonService } from './common.service';

/**
 * The token service which manages the access token and refresh token.
 * These two tokens are retrieved from server and stored on local.
 * Access token will be appended with each http request in the header.
 * Refresh token will be used to retrieve a new access token.
 */
@Injectable()
export class TokenService {
    // The boolean of user selection. 
    // When true the token data will be saved in local storage.
    // When false the token data will be saved in session storage.
    private rememberMe: boolean;

    private token: Token

    constructor(private commonService: CommonService) {
        this.loadRememberMe();
        this.loadToken();
    }

    /**
     * Gets the active refresh token in string.
     */
    get activeAccessToken(): string {
        return this.token.accessToken;
    }

    /**
     * Check if the access token exists. True means exists. False means does NOT exist.
     */
    get hasAccessToken(): boolean {
        return !this.commonService.isNullOrUndefinedOrEmptyString(this.token.accessToken);
    }

    /**
     * Gets the current refresh token in string.
     */
    get activeRefreshToken(): string {
        return this.token.refreshToken;
    }

    /**
     * Check if the refresh token exists. True means exists. False means does NOT exist.
     */
    get hasRefreshToken(): boolean {
        return !this.commonService.isNullOrUndefinedOrEmptyString(this.token.refreshToken);
    }

    /**
     * Checks if current access token is expired or not.
     * @returns True means access token is expired. False means access token is still active.
     */
    get isAccessTokenExpired(): boolean {
        // this.commonService.getCurrentDate() returns the local time.
        // The object retrieved from storage is UTC format.
        // Use new Date(this.token.dateExpired) converts the UTC to local time.
        // That's why we need to create a new date instance for comparion.
        return new Date(this.token.dateExpired) < this.commonService.getCurrentDate();
    }

    /**
     * Sets the rememberMe property based on user selection.
     * @param rememberMe The boolean value of the user selection.
     */
    setRememberMe(rememberMe: boolean): void {
        this.rememberMe = rememberMe;
        this.commonService.saveLocalData(GeneralConfig.localRememberMeStorageKey, rememberMe);
    }

    /**
     * Saves the token based on the data from remote server.
     * @param tokenData The data from remote server. It should contains below properties 
     *                  string access_token
     *                  string refresh_token
     *                  number expires_in (seconds)
     *                  number request_consumed_seconds (seconds)
     */
    saveToken(tokenData: any): void {
        // Gets current time.
        let currentTime = this.commonService.getCurrentDate();

        // When calcuates on local we need to consider the time consumed by the remote request.
        let requestConsumedSeconds = tokenData.request_consumed_seconds;

        // Considers the min value is 1 second.
        if (requestConsumedSeconds < 1) {
            requestConsumedSeconds = 1;
        }

        // Adds the extra seconds as the buffer just in case. 
        // let requestConsumedBufferSeconds = 2;

        // Caculates the total seconds need to be considered as expiration.
        // For example, token expires in 60 seconds, the token retrieving consumes 1 second, the buffer is 2 seconds.
        // Based on the scenario the expired seconds would be 60 - 1 - 2 = 57.
        let totalConsideredSeconds = (tokenData.expires_in - requestConsumedSeconds) * 0.8;

        // Considers the min value is 1 second.
        if (totalConsideredSeconds < 1) {
            totalConsideredSeconds = 1;
        }

        // Calcuates the dateExpired based on current time and the expired duration in seconds.
        // Why we cacluate the dateExpired instead of using the server result, 
        // because we need to consider the possibility that the client local time might dismatch with the server.
        let dateExpired = this.commonService.addSeconds(currentTime, totalConsideredSeconds);

        // Converts the server data to local model.
        this.token = {
            accessToken: tokenData.access_token,
            refreshToken: tokenData.refresh_token,
            dateGenerated: tokenData.generated_at,
            dateExpired: dateExpired,
            dateRetrieved: currentTime,
        };

        if (this.rememberMe) {
            // Removes session data just in case.
            this.commonService.removeSessionData(GeneralConfig.localTokenStorageKey);

            // If users checks 'remember me' the token data will be saved in local storage.
            this.commonService.saveLocalData(GeneralConfig.localTokenStorageKey, this.token);
        } else {
            // Removes the local data just in case.
            this.commonService.removeLocalData(GeneralConfig.localTokenStorageKey);

            // If users does NOT check 'remember me' the token data will be saved in session storage.
            // Onces user closes the browser the session storage will be auto cleared.
            this.commonService.saveSessionData(GeneralConfig.localTokenStorageKey, this.token);
        }
    }

    /**
     * Removes the token from local storage and session storage.
     */
    removeToken(): void {
        // Gets all properties of Token object.
        let pros = Object.getOwnPropertyNames(this.token)

        // Removes all the properties' values.
        for (let i = 0; i < pros.length; i++) {
            this.token[pros[i]] = null;
        }

        // Removes the saved token.
        this.commonService.removeLocalData(GeneralConfig.localTokenStorageKey);
        this.commonService.removeSessionData(GeneralConfig.localTokenStorageKey);
    }

    /**
     * Loads rememberMe property value.
     */
    private loadRememberMe(): void {
        // Tries to load from the local storage.
        this.rememberMe = this.commonService.loadSessionData(GeneralConfig.localRememberMeStorageKey) as boolean;

        // If there is no such value in local storage we will assign a defalut value.
        if (this.commonService.isNullOrUndefined(this.rememberMe)) {
            this.rememberMe = false;
        }
    }

    /**
     * Tries to load the saved token from local storage or session storage based on remembeMe value.
     */
    private loadToken(): void {
        if (this.rememberMe) {
            // If user checks 'remember me' the token will be stored in the local storage for next usage.
            this.token = this.commonService.loadLocalData(GeneralConfig.localTokenStorageKey) as Token;
        } else {
            // If user does not check 'remember me' the token will be stored in the session storage.
            this.token = this.commonService.loadSessionData(GeneralConfig.localTokenStorageKey) as Token;
        }

        // If there is no such value in local storage we will assign a defalut value.
        if (this.commonService.isNullOrUndefined(this.token)) {
            this.token = {} as Token;
        }
    }
}